﻿//1、自我介绍

//2、构造函数和析构函数需要重写吗？
构造函数不能被重写（override），因为重写是针对基类和派生类中具有相同签名的虚函数而言的，而构造函数不是虚函数。
不过，派生类可以定义自己的构造函数，当创建派生类对象时，会先调用基类的构造函数，再调用派生类的构造函数。

一般情况：如果基类的析构函数不是虚析构函数，那么派生类的析构函数不会被重写。当通过基类指针删除派生类对象时，只会调用基类的析构函数，可能会导致派生类对象的资源无法正确释放。
需要重写的情况：为了避免上述问题，当基类指针可能指向派生类对象时，应该将基类的析构函数声明为虚析构函数。这样，在删除基类指针时，会先调用派生类的析构函数，再调用基类的析构函数。

//3、右值引用的作用及原理
实现移动语义
实现完美转发
移动语义的实现原理
移动构造函数和移动赋值运算符使用右值引用作为参数，当一个临时对象（右值）作为参数传递给移动构造函数或移动赋值运算符时，会触发资源的转移。
在移动构造函数中，通常会将源对象的资源指针赋值给目标对象，然后将源对象的资源指针置为 nullptr，避免在源对象析构时释放已经转移的资源。

std::forward 是一个模板函数，它利用了引用折叠规则和模板参数推导来实现完美转发。引用折叠规则如下：
T&& 折叠为 T&
T&&& 折叠为 T&
T&&& 折叠为 T&
T&&&& 折叠为 T&&
通过模板参数推导和引用折叠，std::forward 可以根据原始参数的值类别，将参数以相同的值类别转发给目标函数。

//4、单例模式实现？不用锁怎么保证线程安全
饿汉式单例：
在类加载时就创建单例实例，因此不存在线程安全问题。
#include <iostream>
class Singleton {
private:
    // 构造函数私有化，防止外部实例化
    Singleton() {}
    // 拷贝构造函数和赋值运算符私有化，防止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态成员变量，保存单例实例
    static Singleton instance;

public:
    // 静态成员函数，用于获取单例实例
    static Singleton& getInstance() {
        return instance;
    }
};

// 类外初始化静态成员变量
Singleton Singleton::instance;

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();
    std::cout << (&s1 == &s2) << std::endl;  // 输出 1，表示 s1 和 s2 是同一个实例
    return 0;
}
实现简单，线程安全。
在类加载时就创建实例，可能会导致资源浪费，即使该实例在程序运行过程中可能不会被使用

懒汉式单例：
在第一次使用时才创建单例实例，避免了资源的浪费，但在多线程环境下需要考虑线程安全问题。
#include <iostream>

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance;

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

// 类外初始化静态成员变量
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl;  // 输出 1，表示 s1 和 s2 是同一个实例
    return 0;
}
这种实现方式在单线程环境下可以正常工作，但在多线程环境下，可能会出现多个线程同时判断 instance 为 nullptr，从而创建多个实例的问题。


线程安全的懒汉式单例（双重检查锁定）
#include <iostream>
#include <mutex>

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance;
    static std::mutex mtx;

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
};

// 类外初始化静态成员变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    std::cout << (s1 == s2) << std::endl;  // 输出 1，表示 s1 和 s2 是同一个实例
    return 0;
}

通过双重检查锁定机制，在第一次检查 instance 是否为 nullptr 时不加锁，只有在需要创建实例时才加锁，避免了每次调用 getInstance 方法都加锁带来的性能开销。


//5、智能指针介绍
auto_ptr
unique_ptr
shared_ptr
weak_ptr

//6、share_ptr管理数组资源需要注意什么？删除器怎么实现
注意：
shared_ptr 的默认删除器使用 delete 操作符来释放资源，而对于数组资源，应该使用 delete[]
如果直接使用 std::shared_ptr 管理数组且不指定删除器，会导致未定义行为，因为 delete 只会释放数组的第一个元素，而不会释放整个数组，从而造成内存泄漏。

std::shared_ptr 本身没有像 std::vector 或数组那样直接的下标访问操作符[]。要访问数组中的元素，需要通过指针算术来实现。

std::make_shared 不支持创建数组类型的 std::shared_ptr。因为 std::make_shared 内部使用 new 来分配对象，而不是 new[]，所以无法正确分配数组内存。

实现删除器的方法
函数指针
仿函数
lambda表达式
可以定义一个函数，该函数接受一个指向数组的指针，并使用 delete[] 来释放数组。


//7、lambda表达式的参数绑定的实现原理
Lambda 表达式本质上是一个 匿名的仿函数对象（closure object），在 C++ 中，它被编译器转换为一个 匿名类，并实现 operator() 以支持调用。
auto lambda = [](int x) { return x * 2; };
编译器会转换成类似于
struct Lambda {
    auto operator()(int x) const { return x * 2; }
};
ambda 的参数绑定实际上是对 operator() 形参的传递。


值捕获
int a = 10;
auto lambda = [a](int x) { return x + a; };
引用捕获
int a = 10;
auto lambda = [&a](int x) { return x + a; };
既有值捕获，又有引用捕获
int a = 10, b = 20;
auto lambda = [a, &b](int x) { return x + a + b; };


通过 mutable 允许修改值捕获的变量

Lambda 的底层实现
Lambda 在底层会转换为 类 + operator() 重载，其参数绑定的过程如下：

捕获变量（值捕获则存成员变量，引用捕获存引用）
operator() 形参传递
调用 operator() 处理逻辑



//8、虚函数实现原理？纯虚函数实现原理
虚函数的实现原理：
虚函数（virtual function） 是通过 虚函数表（vtable） 和 虚表指针（vptr） 机制实现的。
虚函数的目的是实现 运行时多态（动态绑定），即在编译时无法确定调用哪个函数，而是根据对象的实际类型在运行时决定。

(2) vtable（虚表）& vptr（虚表指针）
虚函数的实现依赖于 vtable（虚函数表）和 vptr（虚表指针）：

每个包含虚函数的类 都有一个 虚函数表（vtable），存储该类的虚函数指针。
每个对象 都有一个 隐藏的虚表指针（vptr），指向该类的 vtable。
当对象调用虚函数时，通过 vptr 查询 vtable 并调用正确的函数


2. 纯虚函数（Pure Virtual Function）的实现
virtual void show() = 0; // 纯虚函数
纯虚函数的作用

纯虚函数 不提供实现，子类必须 重写 该函数，否则该类依然是抽象类，不能实例化。

纯虚函数 仍然会在 vtable 中占据一个槽位，指向 一个特殊的函数 __cxa_pure_virtual。
__cxa_pure_virtual 是一个 默认提供的错误处理函数，如果在运行时调用了未重写的纯虚函数，会导致程序崩溃。


//9、C++内存空间结构
代码区（Text Segment）
存放内容
存储程序的机器指令
只读，防止修改指令
共享，例如多个进程可以共享相同的代码段

全局 / 静态区（Data Segment）
存放内容
全局变量
静态变量
常量区（字符串字面量等）

程序运行期间一直存在
分为初始化区和未初始化区（BSS）：
已初始化数据段（.data） → 存放已初始化的全局 / 静态变量
未初始化数据段（BSS） → 存放未初始化的全局 / 静态变量，默认初始化为 0

堆（Heap）
存放内容

动态分配的内存（new / malloc）
程序员手动管理（delete / free）
生命周期由程序员控制，可能导致内存泄漏


栈（Stack）
存放内容

局部变量
函数调用信息（返回地址、参数、临时变量）
特点

由编译器自动分配释放
采用 LIFO（后进先出） 方式管理
访问速度快，空间有限，可能发生栈溢出（stack overflow）


10、实习业务介绍  ，难点？


11、设计模式的应用
单例模式：std::cout、数据库连接池
工厂模式：std::make_shared<>


12、哈希表的优缺点  应用场景   自己哪里用到

✅ 优点
查询速度快：平均 O(1) 时间复杂度，适用于大规模数据存储和检索。
插入和删除高效：也是 O(1)，比树结构（如红黑树 O(log n)）更快。
灵活性强：可用于缓存、数据库索引、哈希集合等。
无序存储：不像数组和链表那样需要维护顺序，插入 / 删除不会影响其他元素。
❌ 缺点
哈希冲突：不同键可能映射到同一索引，需要链地址法或开放寻址法解决，可能导致查询性能下降。
额外空间开销：通常比其他数据结构（如数组、链表）占用更多空间，因为需要维护哈希函数和存储指针。
无法保持顺序：哈希表是无序存储的，不能像 std::map（红黑树实现）那样按键排序。
扩展成本高：当哈希表负载因子过高时（一般 > 0.75），需要重新哈希（rehash），搬移所有元素，耗时 O(n)。


算法问题
数据库索引（MySQL 的 Hash 索引）
操作系统页表（虚拟内存地址映射）

在负载均衡式在线 OJ 系统的任务调度中，我用哈希表来快速映射提交用户和服务器负载：
使用 std::unordered_map<int, int> 维护：
user_id->server_id（用户的代码在哪个服务器上编译）
server_id->load（每台服务器的任务数）
实现任务分发：
遍历服务器负载 std::unordered_map<int, int>，找到负载最小的 server_id
更新 user_id->server_id 映射，确保用户后续提交能落在同一台服务器


//13、内存泄漏如何避免
内存泄漏指的是程序分配了堆内存但没有释放，导致内存无法被回收，最终可能耗尽系统资源，影响程序稳定性。
1）使用智能指针
2）尽量避免 new，使用 STL 容器
3）手动 delete 时，确保 nullptr 处理
4）避免 malloc / free，优先使用 new / delete
5）使用 RAII（资源获取即初始化）
6）使用内存检测工具
Valgrind（Linux）：valgrind --leak - check = full . / a.out
ASan（Address Sanitizer）：编译时加 - fsanitize = address
Visual Studio 内存检测工具



14、迭代器类型？  注意什么？  map遍历有什么问题
C++ STL 迭代器（Iterator）是用于遍历容器的抽象指针，根据功能不同，主要有以下几种类型：

输入迭代器（Input Iterator）	            istream_iterator	只支持单向读取一次（如 cin）
输出迭代器（Output Iterator）	            ostream_iterator	只支持单向写入一次（如 cout）
前向迭代器（Forward Iterator）	            forward_list	    可单向遍历，多次读取
双向迭代器（Bidirectional Iterator）	    list、map、set	    可前后遍历
随机访问迭代器（Random Access Iterator）	vector、deque	    可随机访问（支持 it + n）

迭代器使用注意事项
（1）迭代器失效问题
迭代器失效（Dangling Iterator）指的是指向的内存位置已经无效，但仍然在访问。
情况 1：vector 动态扩容
情况 2：erase() 导致迭代器失效
情况 3：遍历 map 时删除元素



map 遍历的特殊问题
（1）不要用[] 遍历   改用 at()
（2）不要在 map 遍历时 erase()



//15、如何获取数组中的中位数
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main()
{
    vector<int> ret(10);
    ret = { 1,2,3,4,5,6,7,8 };
    sort(ret.begin(), ret.end());

    if (ret.size() % 2 == 0)
    {
        cout << (double)(ret[ret.size() / 2] + ret[ret.size() / 2] - 1) / 2 << endl;
    }
    else
    {
        cout << (double)ret[ret.size() / 2] << endl;
    }
    return 0;
}